Comparable et Comparator
On a des ArrayList<Student>, des listes de produits, des collections d'objets — mais comment les trier par moyenne, par prix, par nom ? Java fournit deux interfaces génériques pour ça : Comparable<T> et Comparator<T>.
Comparable<T> — ordre naturel
L'interface Comparable<T> permet à une classe de définir son ordre naturel — la façon dont ses objets se comparent entre eux par défaut.
public interface Comparable<T> {
int compareTo(T autre);
}
La méthode compareTo() retourne :
- un nombre négatif si
thisest inférieur àautre - zéro s'ils sont égaux
- un nombre positif si
thisest supérieur àautre
Exemple : Student trié par moyenne
public class Student implements Comparable<Student> {
private String name;
private int age;
private double average;
public Student(String name, int age, double average) {
this.name = name;
this.age = age;
this.average = average;
}
public String getName() { return name; }
public int getAge() { return age; }
public double getAverage() { return average; }
@Override
public int compareTo(Student autre) {
return Double.compare(this.average, autre.average);
}
@Override
public String toString() {
return name + " (" + average + ")";
}
}
Une fois Comparable implémenté, Collections.sort() sait quoi faire :
List<Student> students = new ArrayList<>(List.of(
new Student("Alice", 20, 85.5),
new Student("Bob", 22, 76.3),
new Student("Charlie", 19, 91.0)
));
Collections.sort(students);
students.forEach(System.out::println);
// Bob (76.3)
// Alice (85.5)
// Charlie (91.0)
Double.compare() plutôt que la soustraction
// MAUVAIS — imprécis avec des double, risque de débordement avec des int
return (int)(this.average - autre.average);
// BON
return Double.compare(this.average, autre.average);
Pour inverser l'ordre (décroissant), il suffit d'inverser les arguments :
return Double.compare(autre.average, this.average); // tri décroissant
Comparator<T> — ordre externe
Comparable définit un seul ordre naturel dans la classe. Mais si on veut trier les mêmes objets de plusieurs façons (par moyenne, par nom, par âge…), on utilise Comparator<T>.
Puisque Comparator<T> est une interface fonctionnelle, on peut l'écrire avec une lambda :
// Trier par nom alphabétique
students.sort((s1, s2) -> s1.getName().compareTo(s2.getName()));
// Trier par moyenne décroissante
students.sort((s1, s2) -> Double.compare(s2.getAverage(), s1.getAverage()));
Définir les comparateurs dans la classe
Comme vu en 4.2, on peut définir les Comparator comme constantes statiques directement dans Student :
public class Student implements Comparable<Student> {
// ...
public static final Comparator<Student> PAR_NOM =
(s1, s2) -> s1.getName().compareTo(s2.getName());
public static final Comparator<Student> PAR_MOYENNE_DESC =
(s1, s2) -> Double.compare(s2.getAverage(), s1.getAverage());
}
students.sort(Student.PAR_NOM);
students.sort(Student.PAR_MOYENNE_DESC);
Tri sur plusieurs critères
Une lambda peut contenir plusieurs instructions pour définir un critère de départage :
// Moyenne décroissante, et en cas d'égalité, par nom alphabétique
students.sort((s1, s2) -> {
int compareMoyenne = Double.compare(s2.getAverage(), s1.getAverage());
if (compareMoyenne != 0) return compareMoyenne;
return s1.getName().compareTo(s2.getName());
});
Résumé : quand utiliser quoi
Comparable<T> | Comparator<T> | |
|---|---|---|
| Où | Dans la classe elle-même | Lambda ou constante statique |
| Ordre | Un seul ordre naturel | Autant d'ordres qu'on veut |
| Méthode | compareTo(T autre) | compare(T o1, T o2) |
| Utilisation | Collections.sort(liste) | liste.sort(comparateur) |
| Idéal pour | Ordre évident (ex: moyenne, date) | Tris multiples |
Lien avec les génériques
Comparable<T> et Comparator<T> sont des interfaces génériques — exactement le même mécanisme vu au Cours 1. Le T garantit qu'on compare des objets du même type.
On peut écrire une méthode générique qui fonctionne avec n'importe quel type qui implémente Comparable :
// <T extends Comparable<T>> signifie : T doit savoir se comparer à lui-même
public static <T extends Comparable<T>> T trouverMax(ArrayList<T> liste) {
T max = liste.get(0);
for (T element : liste) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
Cette méthode fonctionne avec Integer, String, Student — n'importe quel type qui implémente Comparable :
ArrayList<Student> students = new ArrayList<>(List.of(
new Student("Alice", 20, 85.5),
new Student("Bob", 22, 76.3),
new Student("Charlie", 19, 91.0)
));
System.out.println(trouverMax(students)); // Charlie (91.0)